前端进阶之旅前端进阶之旅
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片NEW
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 业务场景题真实业务问题与追问
  • 查漏补缺常见问题解析
  • AI 模拟面试NEW模拟真实面试 + 报告
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
    • AI 定制路线NEW按你的简历现排
    • AI 知识地图NEW串起全站知识点
  • 动态
    • AI 热点NEWAI 每日动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
AI 助手NEW
旧版
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片NEW
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 业务场景题真实业务问题与追问
  • 查漏补缺常见问题解析
  • AI 模拟面试NEW模拟真实面试 + 报告
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
    • AI 定制路线NEW按你的简历现排
    • AI 知识地图NEW串起全站知识点
  • 动态
    • AI 热点NEWAI 每日动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
AI 助手NEW
旧版
  • Vue

    • 从源码解读Vue生命周期
    • 组件的本质
    • 有状态组件的设计
    • 设计 VNode
    • 辅助创建 VNode 的 h 函数
    • 自定义渲染器和异步渲染
    • 渲染器之挂载
    • 渲染器的核心 Diff 算法
    • 渲染器之patch
    • 图解 Vue 响应式原理
    • 图解 Vue 异步更新
    • 剖析 Vue 内部运行机制
    • vue响应式原理模拟
    • vue状态管理之vuex
    • 理解Vue的设计思想及实现Vue
    • diff算法深入
    • vue router vuex原理分析
    • Vue3初探响应式原理.
    • vue2源码分析
    • vue组件化实践
  • React

  • Webpack

  • Node

  • Javascript

  • 综合

完整面试题地址:
作者:程序员poetry
扫码关注作者公众号:「前端进阶之旅」 每天分享技术干货
前端进阶之旅公众号二维码

跟 React 学设计模式|原理篇

文章学习导航
面试总览

30 秒速记

  • 核心判断:理解原理要建立输入、状态变化、输出和失败边界,而不是只记 API 与结论
  • 原理主线:围绕 「高阶组件(HOC):最经典的组件逻辑复用」、「Render Props:逻辑复用的另一」、「有状态组件与无状态组件:“单一职责”原则」 建立输入、状态变化与输出之间的因果关系
  • 文章范围:讲解了 React 中的高阶组件(HOC)等设计模式,介绍了其原理、实现方式及在实际开发中如何复用组件逻辑,帮助开发者更好地理解和应用 React 设计思想。
  • 边界与代价:文章中的实现通常有版本和场景前提,迁移到当前项目时必须先核对运行环境与依赖
  • 工程落地:面试回答按核心机制、执行链路、边界反例和工程验证展开,最后说明取舍

React 中的设计模式,本质上是在控制变化范围的同时复用组件逻辑。 HOC 通过函数包裹组件统一注入能力,render props 则把数据适配权交给调用方,因此面对不同属性名时更灵活。数据处理和界面渲染也应尽量拆到有状态组件与无状态组件中,让两类变化互不牵连。实际开发中我一般优先考虑自定义 Hook,因为它能减少嵌套和属性冲突;但维护旧类组件时,HOC 与 render props 仍有价值。

这篇文章不要按 API 清单来背。先用上面的 Mind Map 建立全局结构,再通过交互 DEMO 观察正常路径和边界路径如何改变状态;阅读正文时重点核对每一步的输入、负责执行的参与者、产生的中间状态以及最终可观察结果。遇到版本敏感结论,要把“历史实现”“当前行为”和“工程兼容策略”分开说明;遇到性能或架构取舍,则用实际指标、失败现象和验证手段支撑判断。

版本校准: 本文若分析 ReactDOM.render、旧生命周期或栈调和,应把它视为理解架构演进的历史路径。React 19 已移除 ReactDOM.render,当前客户端入口使用 createRoot;并发渲染也必须区分可中断的渲染阶段与同步提交阶段。迁移前对照 React 19 官方升级指南。

# 高阶组件(HOC):最经典的组件逻辑复用方式

# 什么是高阶组件

高阶组件(HOC)是 React 中用于复用组件逻辑的一种高级技巧。HOC 自身不是 React API 的一部分,它是一种基于 React 的组合特性而形成的设计模式。——React 官方

高阶函数的概念:接收函数作为输入,或者输出另一个函数的一类函数,就是高阶函数。

相应的,高阶组件指的就是参数为组件,返回值为新组件的函数。没错,高阶组件本质上是一个函数。下面是一个简单的高阶组件示例:

const withProps = (WrappedComponent) => {
    const targetComponent = (props) => (
        <div className="wrapper-container">
            <WrappedComponent {...props} />
        </div>
    );
    return targetComponent;
};
@前端进阶之旅: 代码已经复制到剪贴板

在这段代码中,withProps 就是一个高阶组件。

# 高阶组件是如何实现逻辑复用的?

现在我们考虑这样一种情况:我有一个名为 checkUserAccess 的方法,这个方法专门用来校验用户的身份是否合法,若不合法,那么一部分组件就要根据这个不合法的身份调整自身的展示逻辑(比如查看个人信息界面需要提示“请校验身份”等)。

假如说页面中的 A、B、C、D、E 五个组件都需要甄别用户身份是否合法,那么这五个组件在理论上都需要先请求一遍 checkUserAccess 这个接口。但一个一个对组件进行修改未免太麻烦了,我们期望对“获取 checkUserAccess 接口信息,并通知到对应组件”这层逻辑进行复用,这时候就可以请出高阶组件来帮忙了。

我们可以像下面代码这样在高阶组件中定义这层通用的逻辑:

// 假设 checkUserAccess 已经在 utils 文件中被封装为了一段独立的逻辑
import checkUserAccess from './utils
// 用高阶组件包裹目标组件
const withCheckAccess = (WrappedComponent) => {
    // 这部分是通用的逻辑:判断用户身份是否合法
    const isAccessible = checkUserAccess()  
    // 将 isAccessible(是否合法) 这个信息传递给目标组件
    const targetComponent = (props) => (
        <div className="wrapper-container">
            <WrappedComponent {...props} isAccessible={isAccessible} />
        </div>
    );
    return targetComponent;
};
@前端进阶之旅: 代码已经复制到剪贴板

这样当我们需要为某个组件复用这层请求逻辑的时候,只需要直接用 withCheckAccess 包裹这个组件就可以了。以 A 组件为例,假设 A 组件的原始版本为 AComponent,那么包裹它的形式就是下面代码这样:

const EnhancedAComponent = withCheckAccess(Acomponent);
@前端进阶之旅: 代码已经复制到剪贴板

通过简单地对高阶组件 withCheckAccess 进行引入,EnhancedAComponent 轻松具备了校验用户合法性的能力。这样一来,即便再多出 5 个组件想要引入 checkUserAccess,我们也不会怂——毕竟包裹五个组件和重写五段逻辑的工作量是没法相提并论的

高阶组件不仅能够帮助我们简化逻辑的引入过程,还可以帮助我们规避掉逻辑变更带来的烦琐的修改步骤:假如这段 checkUserAccess 的逻辑是散落在 A、B、C、D、E 这五个组件之中的,那么一旦 checkUserAccess 的判定规则需要修改,我们就得需要去修改五段代码;但现在,checkUserAccess 被抽离进了一个独立的高阶组件里,我们在高阶组件中的一次修改,将在所有被它处理过的组件中生效。

由此可以看出,高阶组件可以帮助我们从根本上减少重复的编写和修改工作,这不仅是高阶组件这一种模式的利好,更是“逻辑复用”这件事情的意义所在。

# Render Props:逻辑复用的另一种思路

术语“render prop”是指一种在 React 组件之间使用一个值为函数的 prop 共享代码的简单技术。——React 官方

# 什么是 render props

render props 是 React 中复用组件逻辑的另一种思路,它在实现上和高阶组件有异曲同工之妙——两者都是把通用的逻辑提取到某一处。区别主要在于使用层面,高阶组件的使用姿势是用“函数”包裹“组件”,而 render props 恰恰相反,它强调的是用“组件”包裹“函数”

一个简单的 render props 可以是这样的,见下面代码:

import React from 'react'  
const RenderChildren = (props) => {
  return(
     <React.Fragment>
        {props.children(props)}
     </React.Fragment>
  );
};
@前端进阶之旅: 代码已经复制到剪贴板

RenderChildren 将渲染它所有的子组件。从这段代码里,你需要把握住两个要点:

  • render props 的载体应该是一个React 组件,这一点是与高阶组件不同的(高阶组件本质是函数);
  • render props 组件正常工作的前提是它的子组件需要以函数形式存在。

第 1 点相对明显一点,你可能会对第 2 点感到迷惑。没关系,我们直接来看 RenderChildren 的使用方式,请看下面代码:

<RenderChildren>         
  {() => <p>我是 RenderChildren 的子组件</p>}       
</RenderChildren>
@前端进阶之旅: 代码已经复制到剪贴板

RenderChildren 本身是一个 React 组件,它可以包裹其他的 React 组件。一般来说,我们习惯于看到的包裹形式是“标签包裹着标签”,也就是下面代码演示的这种效果:

<RenderChildren>         
  <p>我是 RenderChildren 的子组件</p>     
</RenderChildren>
@前端进阶之旅: 代码已经复制到剪贴板

但在 render props 这种模式下,它要求被 render props 组件标签包裹的一定是个函数,也就是所谓的“函数作为子组件传入”。这样一来,render props 组件就可以通过调用这个函数,传递 props,从而实现和目标组件的通信了。

# render props 是如何实现逻辑复用的

这里我仍然以 checkUserAccess 这个场景举例。使用 render props 复用 checkUserAccess 这段逻辑,我们可以这样做,请看下面代码:

// 假设 checkUserAccess 已经在 utils 文件中被封装为了一段独立的逻辑
import checkUserAccess from './utils
// 定义 render props 组件
const CheckAccess = (props) => {
    // 这部分是通用的逻辑:判断用户身份是否合法
    const isAccessible = checkUserAccess()  
    // 将 isAccessible(是否合法) 这个信息传递给目标组件
    return <React.Fragment>
        {props.children({ ...props, isAccessible })}
      </React.Fragment>
};
@前端进阶之旅: 代码已经复制到剪贴板

接下来 CheckAccess 子组件就可以这样获取 isAccessible 的值,见下面代码:

<CheckAccess>
  {
    (props) => {
      const { isAccessible } = props;
      return <ChildComponent {...props} isAccessible={isAccessible} />
    }
  }
</CheckAccess>
@前端进阶之旅: 代码已经复制到剪贴板

到这里,“函数作为子组件传入”这种情况,我们已经了解了它的来龙去脉。但其实,对于  render props 这种模式来说,函数并不一定要作为子组件传入,它也可以以任意属性名传入,只要 render props 组件可以感知到它就行`。

举个例子,我可以允许函数通过一个名为 checkTaget 的属性传入 render props 组件,那么 CheckAccess 组件只需要改写一下它接收函数的形式即可,见下面代码:

// 假设 checkUserAccess 已经在 utils 文件中被封装为了一段独立的逻辑
import checkUserAccess from './utils
// 定义 render props 组件
const CheckAccess = (props) => {
    // 这部分是通用的逻辑:判断用户身份是否合法
    const isAccessible = checkUserAccess()  
    // 将 isAccessible(是否合法) 这个信息传递给目标组件
    return <React.Fragment>
        {props.checkTaget({ ...props, isAccessible })}
      </React.Fragment>
};
@前端进阶之旅: 代码已经复制到剪贴板

在使用 CheckAccess 组件的时候,我们将函数放在 checkTaget 中传入组件即可,见下面代码:

← 如何打造高性能的 React 应用React全部api解读 →

fe
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片NEW
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 业务场景题真实业务问题与追问
  • 查漏补缺常见问题解析
  • AI 模拟面试NEW模拟真实面试 + 报告
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
    • AI 定制路线NEW按你的简历现排
    • AI 知识地图NEW串起全站知识点
  • 动态
    • AI 热点NEWAI 每日动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
AI 助手NEW
旧版
  • Vue

    • 从源码解读Vue生命周期
    • 组件的本质
    • 有状态组件的设计
    • 设计 VNode
    • 辅助创建 VNode 的 h 函数
    • 自定义渲染器和异步渲染
    • 渲染器之挂载
    • 渲染器的核心 Diff 算法
    • 渲染器之patch
    • 图解 Vue 响应式原理
    • 图解 Vue 异步更新
    • 剖析 Vue 内部运行机制
    • vue响应式原理模拟
    • vue状态管理之vuex
    • 理解Vue的设计思想及实现Vue
    • diff算法深入
    • vue router vuex原理分析
    • Vue3初探响应式原理.
    • vue2源码分析
    • vue组件化实践
  • React

  • Webpack

  • Node

  • Javascript

  • 综合